/**
 * This program prints input text to serial port of each other side.
 * Test result: TX, RX work well with 433MHz on STM32-F407 platform.
 * Two features are improved from the original source code.
 * - Synchronous implementation of CC1101::SendPacket()
 * For subsequent packet transmission, now this function returns after packet transmission.
 * - FIFO checking routine changed.
 * Original code uses timer to check FIFO, it may cause resource corruption by timer interrupt.
 */
#include "mbed.h"

#include "CC1101.h"
#include "RingBuffer.h"

#if 1
    #define PRINTF printf
#else
    #define PRINTF(...)
#endif

#define FIFO_CHECK_INTERVAL_US  500000

///////////////////////////////////////////////////
const PinName mosi = PA_7;
const PinName miso = PA_6;
const PinName clk = PB_3;
const PinName csn = PA_4;
// RDmiso --> pin to detect MISO low.
const PinName RDmiso = PC_0;

// pin connected to GDO0 pin of CC1101 for checking that received a new packet
// It related with CC1101 IOCFG0 register.
const PinName gdo0 = PA_9;

// pin for checking that sent a packet
// It related with CC1101 IOCFG02register.
const PinName gdo2 = PB_10;

CC1101 cc1101(mosi, miso, clk, csn, RDmiso, gdo0, gdo2);

// Create a BufferedSerial object to be used by the system I/O retarget code.
static BufferedSerial pc(PA_2, PA_3); // tx, rx 9600);

FileHandle *mbed::mbed_override_console(int fd)
{
    return &pc;
}
DigitalOut led1(PA_5);  // FIFO led
DigitalOut led2(PA_11);  // RX led
DigitalOut led3(PA_12);  // TX led

RingBuffer pcRX(512);   // ring buffer for the pc RX data
RingBuffer pcTX(512);   // ring buffer for the pc TX data
Timeout pcRXtimeout;
Timeout led2timeout;
Timeout led3timeout;
unsigned char buffer[64];

///////////////////////////////////////////////////
static uint32_t compute_elapse_us(uint32_t prev_tick, uint32_t cur_tick)
{
    if (prev_tick <= cur_tick)
    {
        return cur_tick - prev_tick;
    }
    // tick wrap around happens, it assumes tick start from 0.
    return cur_tick;
}
///////////////////////////////////////////////////
void led2timeout_func()
{
    led2 = 0;
    led2timeout.detach();
}
///////////////////////////////////////////////////
void led3timeout_func()
{
    led3 = 0;
    led3timeout.detach();
}
///////////////////////////////////////////////////
void pcRXtimeout_func()         // function for transmiting the RF packets - empty the pcRX ring buffer
{
    unsigned char txlength;
    txlength = 0;
    while (pcRX.use() > 0)
    {
        led2 = 1;
        buffer[txlength] = pcRX.getc();
        txlength++;
        led2timeout.attach(&led2timeout_func, 0.050f);  // for switch off the led
    }
    if (txlength)
    {
        cc1101.SendPacket(buffer, txlength);    // tx packet
    }
    pcRXtimeout.detach();
}
///////////////////////////////////////////////////
void check_FIFO()           // check the status of the CC1101 to see FIFO error
{
    unsigned char chip_status_rx, chip_status_tx;
    led1 = !led1;
    chip_status_rx = cc1101.ReadChipStatusRX();  // check the rx status
    if ((chip_status_rx & CHIP_STATE_MASK) == CHIP_STATE_RXFIFO_OVERFLOW)    // if rx overflow flush the rx fifo
    {
        PRINTF("*RXFIFO_OVERFLOW->Flush\r\n");
        cc1101.FlushRX();
    }
    if ((chip_status_rx & CHIP_STATE_MASK) == CHIP_STATE_IDLE)               // if state is idle go to rx state again
    {
        // Basically we don't need to set RXMode here because RXOFF_MODE and TXOFF_MODE of CC1101 are RX mode.
        // But we still need to check IDLE because it can be IDEL by flushing FIFO.
        PRINTF("*RX MODE\r\n");
        cc1101.RXMode();
    }
    chip_status_tx = cc1101.ReadChipStatusTX();  // check the tx status
    if ((chip_status_tx & CHIP_STATE_MASK) == CHIP_STATE_TXFIFO_UNDERFLOW)   // if tx underflow flush the tx fifo
    {
        PRINTF("*XFIFO_UNDERFLOW->Flush\r\n");
        cc1101.FlushTX();
    }
    PRINTF("[%8u]Chip RX=%02x TX=%02x\r\n", us_ticker_read() / 1000, chip_status_rx, chip_status_tx);
}
///////////////////////////////////////////////////
int main()
{
    unsigned char rxlength, i;
    uint32_t saved_tick = 0, cur_tick;
	pc.set_baud(115200);
    pc.set_format(
        /* bits */ 8,
        /* parity */ BufferedSerial::None,
        /* stop bit */ 1
    );

    // print to the console using the `serial_port` object.
    PRINTF(
        "Mbed OS version %d.%d.%d\n",
        MBED_MAJOR_VERSION,
        MBED_MINOR_VERSION,
        MBED_PATCH_VERSION
    );
    PRINTF("build at " __TIME__ "\r\n");
    pcRX.clear();
    pcTX.clear();
    cc1101.init();
    while (1)
    {
        cur_tick = us_ticker_read();
        if (compute_elapse_us(saved_tick, cur_tick) > FIFO_CHECK_INTERVAL_US)
        {
            check_FIFO();
            saved_tick = cur_tick;
        }
        if (cc1101.GetGDO0())     // rx finished and CRC OK read the new packet
        {
            rxlength = sizeof(buffer);
            if (cc1101.ReceivePacket(buffer, &rxlength) == 1)   // read the rx packet
            {
                led3 = 1;
                for (i = 0; i < rxlength; i++)
                {
                    pcTX.putc(buffer[i]);    // store the packet to the pcTX ring buffer
                }
                led3timeout.attach(&led3timeout_func, 0.050);   // for switch off the led
            }
        }
        if (pcTX.use() > 0)         // check if we have data to transmit to pc
        {
            char data;
            data = pcTX.getc();
            pc.write(&data,1);    // get the data from the ring buffer and transmit it to the pc
        }
        if (pc.readable())          // check if we received new data from the pc
        {
            char data;
            data = pc.read(&data,1);
            pcRX.putc(data);   // put the data to the pcRX buffer and wait until 20ms passed till the last byte before tx the packet in RF
            if (pcRX.use() > 20)        // if more than 20 bytes received then tx the packet in RF
            {
                pcRXtimeout_func();
            }
            else
            {
                pcRXtimeout.attach(&pcRXtimeout_func, 0.020);
            }
        }
    }
}
///////////////////////////////////////////////////

